/*
 * Middleware
 * Copyright (C) 2010/11
 * Riccardo Pulvirenti, Giuseppe Ravidà, Andrea Tino
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANDABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses
 */

/*!
 * File: worker_discovery.cpp
 */

#include <iostream>
#include <string>
#include <exception>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/thread.hpp>
#include <cstdlib>

#include "worker_discovery.hpp"
#include "performance_index.hpp"
#include "utils.hpp"
#include "workerdescriptor_proxy_factory.hpp"
#include "common_types.hpp"

using namespace middleware;

//-----------------------------------------------------------------------------
// Constructors and destructor
/* Constructor with configuration */
WorkerDiscovery::WorkerDiscovery(PeersKnowledge PK, DiscoveryConfig conf) {
	this->_config.disc_ip_tow_bind = conf.disc_ip_tow_bind;
	this->_config.disc_port_tow_bind = conf.disc_port_tow_bind;
	this->_pk = PK;
	this->_config.log_postfix = conf.log_postfix;
	// Logger
	std::string logfile = std::string("logfile_disc_") + conf.log_postfix;
	this->_logger.set_log_filename(logfile);
	this->_logger.set_log_tag("DISC");
}
/* Destructor */
WorkerDiscovery::~WorkerDiscovery() {
	// All members created statically.
}
//-----------------------------------------------------------------------------
// Private members
/* Gets a random port */
std::string WorkerDiscovery::createPort() {
	return utils::GetRandomIntegralNumberStr(kWorkerDiscoveryMinManPort,
			kWorkerDiscoveryMaxManPort);
}
/* Thread execution flow */
void WorkerDiscovery::exec() {
	// Execution flow for Discovery
	this->bonjour();
	std::cout << kWorkerDiscoveryEtq << "Running!" << std::endl;
	std::cout << kWorkerDiscoveryEtq << "Configuring proxy factory..." <<
			std::endl;
	WorkerDescriptorProxyFactory proxy_factory(this->_config.disc_ip_tow_bind,
			this->_config.disc_port_tow_bind);
	std::cout << kWorkerDiscoveryEtq << "Getting proxy..." << std::endl;
	WorkerDescriptorManagerProxy* proxy = proxy_factory.getProxy();
	/* Listening for worker descriptor arrival, updating knowledge */
	try {
		// Cycling and waiting on some workerdescriptor to arrive
		for (;;) {
			std::cout << kWorkerDiscoveryEtq <<
					"Worker descriptor notifies loop, "<<
					"waiting incoming worker descriptor..." << std::endl;
			WorkerDescriptor wd = proxy->recv_workerdescriptor();
			std::cout << kWorkerDiscoveryEtq <<
					"Arrived WorkerDesc: peer at IP=" <<
					wd.get_address().get_ip() << ", man_PORT=" <<
					wd.get_address().get_port() << ", disc_PORT=" <<
					wd.get_address().get_port_disc() << " and ui_PORT=" <<
					wd.get_address().get_port_ui() << "; having new PI=" <<
					wd.PI() << ". Now managing...." << std::endl;
			this->_logger.Print(std::string("Arrived worker descriptor ") +
					std::string("of peer at IP=") + wd.get_address().get_ip() +
					std::string(" providing its interface on man_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port()) +
					std::string(", on disc_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port_disc()) +
					std::string(" and on ui_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port_ui()) +
					std::string(" and having a new PI=") +
					boost::lexical_cast<std::string>(wd.PI()) +
					std::string("."));
			// A thread will manage this
			boost::shared_ptr<boost::thread>(new boost::thread(
					boost::bind(&WorkerDiscovery::ManageWorkerDescriptor,
							this, wd)));
			//this->ManageWorkerDescriptor(wd); /* Managing this flow (singlethread) */
		}
	} catch(std::exception& e) {
		std::cout << kWorkerDiscoveryEtq << "Error: " << e.what() << std::endl;
		std::cout << kWorkerDiscoveryEtq << "Now exiting!" << std::endl;
		return;
	}
}
/* Can be a thread, this function operates in order to add/update a
 * worker descriptor */
void WorkerDiscovery::ManageWorkerDescriptor(WorkerDescriptor wd) {
	std::cout << kWorkerDiscoveryEtq <<
			"WorkerDescManager: Managing WorkerDesc: (IP=" <<
			wd.get_address().get_ip() << ", man_PORT=" <<
			wd.get_address().get_port() << ", disc_PORT=" <<
			wd.get_address().get_port_disc() << ", ui_PORT=" <<
			wd.get_address().get_port_ui() << " | new_PI=" <<
			wd.PI() << ")" << std::endl;
	// Cycle all peers and update or add this descriptor
	/* Locking and entering critical region */
	boost::mutex::scoped_lock lck(*(this->_pk.ptr_to_worker_descriptors_mutex));
	PeersSet::iterator it;
	Address addr = wd.get_address();
	bool found = false;
	// We must browse all worker descriptors
	for (it = this->_pk.ptr_to_worker_descriptors->begin();
			it < this->_pk.ptr_to_worker_descriptors->end(); it++) {
		if (addr == it->get_address()) {
			// Found
			it->set_capabilities(wd.get_capabilities());
			it->set_status(wd.get_status());
			found = true;
			std::cout << kWorkerDiscoveryEtq <<
					"WorkerDescManager: Old worker descriptor replaced!" <<
					std::endl;
			this->_logger.Print(std::string("Updated old worker descriptor ") +
					std::string("of peer at IP=") + wd.get_address().get_ip() +
					std::string(" providing its interface on man_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port()) +
					std::string(", on disc_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port_disc()) +
					std::string(" and on ui_PORT=") +
					boost::lexical_cast<std::string>(wd.get_address().get_port_ui()) +
					std::string(" and having an updated PI=") +
					boost::lexical_cast<std::string>(wd.PI()) +
					std::string("."));
			break;
		}
	}
	if (!found) {
		// If this is a new address, then this is a new peer, add it
		this->_pk.ptr_to_worker_descriptors->push_back(wd);
		std::cout << kWorkerDiscoveryEtq <<
				"WorkerDescManager: New worker descriptor added!" <<
				std::endl;
		this->_logger.Print(std::string("Added new worker descriptor ") +
				std::string("of peer at IP=") + wd.get_address().get_ip() +
				std::string(" providing its interface on man_PORT=") +
				boost::lexical_cast<std::string>(wd.get_address().get_port()) +
				std::string(", on disc_PORT=") +
				boost::lexical_cast<std::string>(wd.get_address().get_port_disc()) +
				std::string(" and on ui_PORT=") +
				boost::lexical_cast<std::string>(wd.get_address().get_port_ui()) +
				std::string(" and having a PI=") +
				boost::lexical_cast<std::string>(wd.PI()) +
				std::string("."));
	}
	lck.unlock(); /* Unlock */
}
/* Gets the proper worker, invoked through proxy by Manager */
WorkerDescriptor WorkerDiscovery::worker_getter(const TaskDescriptor& td) {
	return this->GetCurrentBestWorkerDesc();
}
/* Gets the best worker */
WorkerDescriptor WorkerDiscovery::GetCurrentBestWorkerDesc() {
	// Cycle in Peers Knowledge getting the workerdesc having the highest PI
	WorkerDescriptor wd;
	boost::mutex::scoped_lock lck(
			*(this->_pk.ptr_to_worker_descriptors_mutex)); /* Locking */
	PeersSet::iterator it = this->_pk.ptr_to_worker_descriptors->begin();
	PeersSet::iterator highest_it = this->_pk.ptr_to_worker_descriptors->begin();
	calculus::PIType highest_pi = 0;
	// We must browse all worker descriptors
	for (it = this->_pk.ptr_to_worker_descriptors->begin();
			it < this->_pk.ptr_to_worker_descriptors->end();
			it++) {
		//std::cout << "PI=" << it->PI() << std::endl;
		if (it->PI() > highest_pi) {
			highest_pi = it->PI();
			highest_it = it;
		}
	}
	wd = *highest_it;
	lck.unlock(); /* Unlock */
	// Here I have the final value
	return wd;
}
/* Bonjour */
void WorkerDiscovery::bonjour() {
	std::cout << kWorkerDiscoveryEtq <<
			"Initialization sequence now started..." <<	std::endl;
	std::cout << kWorkerDiscoveryEtq <<
			"Configuration loaded: WorkerDescsArrivalGate_IP: this=" <<
			this->_config.disc_ip_tow_bind <<
			", WorkerDescsArrivalGate_IP: " <<
			this->_config.disc_port_tow_bind << std::endl;
	std::cout << kWorkerDiscoveryEtq << "Initialization completed!" <<
			std::endl;
}
